#!/usr/bin/python
# -*- coding: utf-8 -*-

u"""
"""

__version__ = '$Id$'


import sys
import os

import unittest
import warnings

# find the local copy
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))

from bipy import timing

class TestGameTimeClock(unittest.TestCase):

    def setUp(self):
        self.game_time = timing.GameTime()
        self.game_time.event_update += self.tick
        self.args = None
        self.kwargs = None

    def tick(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

    def test_00_tick(self):
        self.game_time.update(0, 0)
        self.assertEquals(self.args, (0, 0, 0, 0), u'tick failed')
        self.game_time.update(0.5, 0.5)
        self.assertEquals(self.args, (0.5, 0.5, 0.5, 0.5), u'0.5 tick failed')
        self.game_time.update(0.5, 1.0)
        self.assertEquals(self.args, (0.5, 1.0, 0.5, 1.0), u'1.0 tick failed')
        self.game_time.update(0.5123, 1.5123)
        self.assertEquals(self.args, (0.5123, 1.5123, 0.5123, 1.5123), u'0.5 tick failed')

    def test_01_time_shrinkage_slower(self):
        self.game_time.tick_speed = 0.5
        self.game_time.update(0, 0)
        self.assertEquals(self.args, (0, 0, 0, 0), u'tick failed')
        self.game_time.update(1.0, 1.0)
        self.assertEquals(self.args, (0.5, 0.5, 1.0, 1.0), u'0.5 tick failed')
        self.game_time.update(1.0, 2.0)
        self.assertEquals(self.args, (0.5, 1.0, 1.0, 2.0), u'1.0 tick failed')

    def test_02_time_dilatation_faster(self):
        self.game_time.tick_speed = 2.0
        self.game_time.update(0, 0)
        self.assertEquals(self.args, (0, 0, 0, 0), u'tick failed')
        self.game_time.update(1.0, 1.0)
        self.assertEquals(self.args, (2.0, 2.0, 1.0, 1.0), u'0.5 tick failed')
        self.game_time.update(1.0, 2.0)
        self.assertEquals(self.args, (2.0, 4.0, 1.0, 2.0), u'1.0 tick failed')

    def test_03_negative_factor(self):
        try:
            warnings.simplefilter('error')
            self.failUnlessRaises(Exception, self.game_time._set_factor, -1)
        finally:
            warnings.resetwarnings()
        self.game_time.tick_speed = -1
        self.game_time.update(30, 30)
        print self.args
        self.assertEquals(self.args, (-30, -30, 30, 30), u'tick failed')

    def test_04_advance_only_when_updated(self):
        self.game_time.update(30, 30)
        self.assertEquals(self.args, (30, 30, 30, 30), u'tick failed')
        self.game_time.update(30, 900)
        self.assertEquals(self.args, (30, 60, 30, 900), u'tick failed')
        self.game_time.tick_speed = 2.0
        self.game_time.update(30, 930)
        self.assertEquals(self.args, (60, 120, 30, 930), u'tick failed')

    def test_05_negative_tick_speed(self):
        self.game_time.tick_speed = -1.0
        self.game_time.update(30, 30)
        self.assertEquals(self.args, (-30, -30, 30, 30), u'tick failed')
        self.game_time.update(30, 900)
        self.assertEquals(self.args, (-30, -60, 30, 900), u'tick failed')
        self.game_time.tick_speed = -2.0
        self.game_time.update(30, 930)
        self.assertEquals(self.args, (-60, -120, 30, 930), u'tick failed')

#-------------------------------------------------------------------------------
class TestGameTimeScheduler(unittest.TestCase):

    def setUp(self):
        self.game_time = timing.GameTime()
        self.cb_called = False
        self.cb2_called = False
        self.cb_count = 0
        self.cb2_count = 0

    def cb(self, *args, **kwargs):
        self.cb_called = True
        self.args = args
        self.kwargs = kwargs
        self.cb_count += 1

    def cb2(self, *args, **kwargs):
        self.cb2_called = True
        self.cb2_count += 1

    def test_00_callback(self):
        self.game_time.schedule(50, self.cb, 1, name='a')
        self.game_time.update(10, 10)
        self.failIf(self.cb_called, "callback should not have been called")
        self.game_time.update(41, 51)
        self.failUnless(self.cb_called, "callback should have been called")
        self.failUnless(self.args == (1, ), "args are not passed correctly")
        self.failUnless(self.kwargs == {'name':'a'},  "kwargs not passed correctly")

    def test_01_multi_callbacks(self):
        self.game_time.schedule(10, self.cb)
        self.game_time.schedule(20, self.cb2)
        self.game_time.update(30, 30)
        self.failUnless(self.cb_called, "callback should have been called")
        self.failUnless(self.cb2_called, "callback2 should have been called")

    def test_02_unschedule(self):
        self.game_time.schedule(10, self.cb)
        self.game_time.schedule(20, self.cb2)
        self.game_time.update(15, 15)
        self.game_time.unschedule(self.cb2)
        self.failUnless(self.cb_called, "callback should have been called")
        self.failIf(self.cb2_called, "callback2 should have not been called")

    def test_03_schedule_repeated(self):
        self.game_time.schedule_repeated(20, self.cb, 'aa', name='bb')
        self.game_time.update(15, 15)
        self.failUnless(self.cb_count == 0, "callback should not have been called")
        self.game_time.update(15, 30)
        self.failUnless(self.cb_count == 1, "callback should have been called 1")
        self.game_time.update(15, 45)
        self.failUnless(self.cb_count == 2, "callback should have been called 2")
        self.game_time.update(5, 50)
        self.failUnless(self.cb_count == 2, "callback should have been called 3")
        self.game_time.update(50, 100)
        self.failUnless(self.cb_count == 5, "callback should have been called 4")
        print self.args, self.kwargs
        self.failUnless(self.args == ('aa', ), "wrong args passed")
        self.failUnless(self.kwargs == {'name':'bb'}, "wrong kwargs passed")

    def test_04_schedule_repeated_dilated(self):
        for factor in [1, 0.5, 2,  0.33]:
            self.setUp()
            inv_fac = 1.0 / factor
            self.game_time.tick_speed = factor
            self.game_time.schedule_repeated(20, self.cb, 'aa', name='bb')
            self.game_time.update(inv_fac * 15, inv_fac * 15)
            self.failUnless(self.cb_count == 0, "callback should not have been called using factor %s" % str(factor))
            self.game_time.update(inv_fac * 15, inv_fac * 30)
            self.failUnless(self.cb_count == 1, "callback should have been called 1 using factor %s" % str(factor))
            self.game_time.update(inv_fac * 15, inv_fac * 45)
            self.failUnless(self.cb_count == 2, "callback should have been called 2 using factor %s" % str(factor))
            self.game_time.update(inv_fac * 5, inv_fac * 50)
            self.failUnless(self.cb_count == 2, "callback should have been called 3 using factor %s" % str(factor))
            # testing time leaping or _reschedule
            self.game_time.update(inv_fac * 50, inv_fac * 100)
            self.failUnless(self.cb_count == 5, "callback should have been called 4 using factor %s" % str(factor))
            self.failUnless(self.args == ('aa', ), "wrong args passed using factor %s" % str(factor))
            self.failUnless(self.kwargs == {'name':'bb'}, "wrong kwargs passed using factor %s" % str(factor))

    def test_05_remove_repeated(self):
        self.game_time.schedule_repeated(20, self.cb, 'aa', name='bb')
        self.game_time.schedule_repeated(20, self.cb2, 'aa', name='bb')
        self.game_time.update(15, 15)
        self.failUnless(self.cb_count == 0, "callback should not have been called")
        self.failUnless(self.cb2_count == 0, "callback2 should not have been called")
        self.game_time.update(15, 30)
        self.failUnless(self.cb_count == 1, "callback should have been called 1")
        self.failUnless(self.cb2_count == 1, "callback2 should have been called 1")
        self.game_time.update(15, 45)
        self.failUnless(self.cb_count == 2, "callback should have been called 2")
        self.failUnless(self.cb2_count == 2, "callback2 should have been called 2")
        self.game_time.unschedule(self.cb)
        self.game_time.update(45, 90)
        self.failUnless(self.cb_count == 2, "callback should not been called 3")
        self.failUnless(self.cb2_count == 4, "callback2 has not been called 3")

    def test_06_clear(self):
        self.game_time.schedule(20, self.cb2)
        self.game_time.schedule_repeated(20, self.cb)
        self.game_time.clear()
        self.game_time.update(50, 50)
        self.failUnless(self.cb_called == False, "cb should not have been called")
        self.failUnless(self.cb2_called == False, "cb2 should not have been called")

    def test_07_timepsan_converts(self):
        self.game_time.tick_speed = 0.5 # run half as fast as real time
        gdt = self.game_time.timespan_real_to_game_time(10)
        self.failUnless(20 == gdt, "game time span expressed as real time is wrong")
        dt = self.game_time.timespan_game_time_to_real(20)
        self.failUnless(10 == dt, "real time of a game time span is wrong")

    def remove_cb(self, *args, **kwargs):
        self.game_time.unschedule(self.remove_cb)
        self.rcb += 1

    def test_08_unschedule_from_cb(self):
        self.rcb = 0
        self.game_time.schedule_repeated(20, self.remove_cb)
        self.game_time.update(20, 20)
        self.failUnless(1 == self.rcb, "remove_cb not called")
        self.game_time.update(20, 40)
        self.failUnless(1 == self.rcb, "remove_cb not removed")

    def remove_cb2(self, *args, **kwargs):
        self.rcb += 1
        return self.game_time.SCHEDULE_STOP

    def test_09_unschedule_from_cb_return_way(self):
        self.rcb = 0
        self.game_time.schedule_repeated(20, self.remove_cb2)
        self.game_time.update(20, 20)
        self.failUnless(1 == self.rcb, "remove_cb not called")
        self.game_time.update(20, 40)
        self.failUnless(1 == self.rcb, "remove_cb not removed")

    def test_10_unschedule_from_cb_behavior(self):
        self.rcb = 0
        self.game_time.schedule_repeated(20, self.cb)
        self.game_time.unschedule(self.cb)
        self.game_time.schedule_repeated(20, self.cb)
        self.game_time.update(20, 20)
        self.failUnless(1 == self.cb_count, "cb not called")
        self.game_time.update(20, 40)
        self.failUnless(2 == self.cb_count, "cb not called 2")

# TODO: prevent tick_speed == 0, impl a pause func instead (toggle_pause(), is_paused())

#-------------------------------------------------------------------------------
import time
class TestStopWatch(unittest.TestCase):

    def setUp(self):
        self.stw = timing.StopWatch()

    def test_00_elapsed_at_start(self):
        self.failIf(0 != self.stw.elapsed, u'elapsed time first time not 0!')

    def test_01_start_directly(self):
        self.stw = timing.StopWatch(True)
        time.sleep(0.2)
        self.failIf(0 == self.stw.elapsed, u'elapsed should be growing!')
        interval = self.stw.stop()
        time.sleep(0.2)
        self.failUnless(interval == self.stw.elapsed, u'elapsed should return same time as when it was stoped')

    def test_02_repeated_starts(self):
        self.stw.start()
        t1 = self.stw.elapsed
        time.sleep(0.2)
        self.stw.start()
        t2 = self.stw.elapsed
        self.failUnless(t2 > t1, u'elapsed should grow')

    def test_03_reset(self):
        self.stw.start()
        t1 = self.stw.elapsed
        time.sleep(0.2)
        self.stw.start(True)
        t2 = self.stw.elapsed
        self.failIf(t2 > t1, u'elapsed should grow')
        

#-------------------------------------------------------------------------------
class ScheulderMock(object):

    def __init__(self):
        self._time = 0
        self._dt = 0
        self._func = None

    def _get_time(self):
        return self._time

    def _schedule_once(self, dt, func):
        self._dt = dt
        self._func = func

    def _unschedule(self, func):
        self._func = None

    def cb(self):
        if self._func:
            self._func()

#-------------------------------------------------------------------------------
class TestScheduler(unittest.TestCase):

    def setUp(self):
        self.mock = ScheulderMock()
        #schedule_once_func, unschedule_func, get_time_func
        self.scheduler = timing.Scheduler()
        self.scheduler.on_activate(self.mock)
        self.called_func = False
        self.called_func2 = False

    def func(self, *args, **kwargs):
        self.called_func = True
        self.args = args
        self.kwargs = kwargs

    def func2(self, *args, **kwargs):
        self.called_func2 = True
        self.args2 = args
        self.kwargs2 = kwargs

    def test_00_schedule(self):
        self.scheduler.schedule(100, self.func)
        self.failUnlessEqual(self.mock._dt, 100, u'dt not passed')
        self.failUnlessEqual(self.mock._func, self.scheduler._update, u'wrong func scheduled')
        self.scheduler.schedule(55, self.func, 0, a=1)
        self.failUnlessEqual(self.mock._dt, 55, u'dt not passed')
        self.failUnlessEqual(self.mock._func, self.scheduler._update, u'wrong func scheduled')
        

    def test_01_schedule_cb(self):
        self.scheduler.schedule(100, self.func)
        self.mock._time = 100
        self.mock.cb()
        self.failUnless(self.called_func, u'function not called')
        self.scheduler.schedule(100, self.func, 'args', named='kwargs')
        self.mock._time = 200
        self.mock.cb()
        self.failUnlessEqual(self.args, ('args',), u'wrong args')
        self.failUnlessEqual(self.kwargs, {'named':'kwargs'}, u'wrong kwargs')

    def test_02_order(self):
        self.scheduler.schedule(20, self.func)
        self.scheduler.schedule(100, self.func2)
        self.mock._time = 20
        self.mock.cb()
        self.failUnless(self.called_func, u'function not called')
        self.failIf(self.called_func2, u'function not called')
        self.failUnlessEqual(self.mock._dt, 80, u'delta dt not updated')
        self.mock._time = 100
        self.mock.cb()
        self.failUnless(self.called_func, u'function not called')
        self.failUnless(self.called_func2, u'function not called')
        self.failIf(self.mock._func, u'func not canceled')

    def test_03_order2(self):
        self.scheduler.schedule(100, self.func)
        self.scheduler.schedule(70, self.func2)
        self.mock._time = 70
        self.mock.cb()
        self.failUnless(self.called_func2, u'function not called')
        self.failIf(self.called_func, u'function not called')
        self.failUnlessEqual(self.mock._dt, 30, u'delta dt not updated')
        self.mock._time = 100
        self.mock.cb()
        self.failUnless(self.called_func, u'function not called')
        self.failUnless(self.called_func2, u'function not called')
        self.failIf(self.mock._func, u'func not canceled')

    def test_04_delayed(self):
        self.scheduler.schedule(20, self.func)
        self.scheduler.schedule(100, self.func2)
        self.mock._time = 25 # 5 ms too late
        self.mock.cb()
        self.failUnlessEqual(self.mock._dt, 75, u'delta dt not updated')

    def test_05_heavy_delayed(self):
        self.scheduler.schedule(20, self.func)
        self.scheduler.schedule(100, self.func2)
        self.mock._time = 200
        self.mock.cb()
        self.failUnless(self.called_func, u'function not called')
        self.failUnless(self.called_func2, u'function2 not called')

    def test_06_too_early(self):
        self.scheduler.schedule(20, self.func)
        self.scheduler.schedule(100, self.func2)
        self.mock._time = 17
        self.mock.cb()
        self.failIf(self.called_func, u'function not called')
        self.failIf(self.called_func2, u'function not called')
        self.failUnlessEqual(self.mock._dt, 3, u'delta dt not updated')

    def test_07_too_early_TOL(self):
        self.scheduler.TOL = 5
        self.scheduler.schedule(20, self.func)
        self.scheduler.schedule(100, self.func2)
        self.mock._time = 14
        self.mock.cb()
        self.failIf(self.called_func, u'function not called')
        self.failIf(self.called_func2, u'function not called')
        self.failUnlessEqual(self.mock._dt, 6, u'delta dt not updated')
        self.mock._time = 18
        self.mock.cb()
        self.failUnless(self.called_func, u'function not called')
        self.failIf(self.called_func2, u'function not called')
        self.failUnlessEqual(self.mock._dt, 82, u'delta dt not updated')

    def test_08_unschedule(self):
        self.scheduler.schedule(20, self.func)
        self.mock._time = 13
        self.mock.cb()
        self.failIf(self.called_func, u'function not called')
        self.failUnlessEqual(self.mock._dt, 7, u'delta dt not updated')
        self.scheduler.unschedule(self.func)
        self.mock._time = 20
        self.mock.cb()
        self.failIf(self.called_func, u'function called after unschedule')
        self.failIf(self.mock._func, u'not unscheduled')

    def test_09_unschedule2(self):
        self.scheduler.schedule(20, self.func)
        self.scheduler.schedule(100, self.func2)
        self.mock._time = 13
        self.mock.cb()
        self.failIf(self.called_func, u'function called')
        self.failIf(self.called_func2, u'function called')
        self.failUnlessEqual(self.mock._dt, 7, u'delta dt not updated')
        self.scheduler.unschedule(self.func)
        self.mock._time = 20
        self.mock.cb()
        self.failIf(self.called_func, u'function called after unschedule')
        self.failIf(self.called_func2, u'function called after unschedule')
        self.failUnless(self.mock._func, u'unscheduled')

    def test_10_unschedule3(self):
        self.scheduler.schedule(20, self.func)
        self.scheduler.schedule(100, self.func2)
        self.mock._time = 13
        self.mock.cb()
        self.failIf(self.called_func, u'function called')
        self.failIf(self.called_func2, u'function called')
        self.failUnlessEqual(self.mock._dt, 7, u'delta dt not updated')
        self.scheduler.unschedule(self.func2)
        self.mock._time = 20
        self.mock.cb()
        self.failUnless(self.called_func, u'function not called')
        self.failIf(self.called_func2, u'function called after unschedule')
        self.failIf(self.mock._func, u'not unscheduled')

    def test_11_pause(self):
        self.scheduler.schedule(20, self.func)
        self.scheduler.schedule(100, self.func2)
        self.mock._time = 15
        self.mock.cb()
        self.scheduler.on_deactivate()
        self.mock._time = 70
        self.scheduler.on_activate(self.mock)
        self.mock.cb()
        self.failUnlessEqual(self.mock._dt, 5, u'wrong scheduling time')
        

if __name__ == '__main__':
    unittest.main()
